package org.hxzon.cyclone.pcontacts;

import org.hxzon.cyclone.Particle;
import org.hxzon.cyclone.Vector3;

/**
 * A Contact represents two objects in contact (in this case
 * ParticleContact representing two Particles). Resolving a
 * contact removes their interpenetration, and applies sufficient
 * impulse to keep them apart. Colliding bodies may also rebound.
 *
 * The contact has no callable functions, it just holds the
 * contact details. To resolve a set of contacts, use the particle
 * contact resolver class.
 */
//粒子碰撞
public class ParticleContact {

    //两个粒子，如果第2个为空，则为与场景碰撞
    public Particle[] particle = new Particle[2];

    //恢复系数，restitution coefficient
    public double restitution;

    //碰撞法线
    public Vector3 contactNormal;

    //嵌入深度
    public double penetration;

    /**
     * Holds the amount each particle is moved by during interpenetration
     * resolution.
     */
    public Vector3[] particleMovement = new Vector3[2];

    //碰撞处理，即更新两个粒子的速度和位置
    protected void resolve(double duration) {
        resolveVelocity(duration);
        resolveInterpenetration(duration);
    }

    //计算碰撞后的分离速度
    protected double calculateSeparatingVelocity() {
        Vector3 relativeVelocity = particle[0].getVelocity().copy();
        if (particle[1] != null) {
            relativeVelocity.minusUpdate(particle[1].getVelocity());
        }
        return relativeVelocity.scalarProduct(contactNormal);//v*v
    }

    //设置两个粒子碰撞后的速度
    private void resolveVelocity(double duration) {
        // Find the velocity in the direction of the contact
        double separatingVelocity = calculateSeparatingVelocity();

        // Check if it needs to be resolved
        if (separatingVelocity > 0) {
            // The contact is either separating, or stationary - there's
            // no impulse required.
            return;
        }

        // Calculate the new separating velocity
        double newSepVelocity = -separatingVelocity * restitution;

        // Check the velocity build-up due to acceleration only
        Vector3 accCausedVelocity = particle[0].getAcceleration().copy();
        if (particle[1] != null) {
            accCausedVelocity.minusUpdate(particle[1].getAcceleration());
        }
        double accCausedSepVelocity = accCausedVelocity.scalarProduct(contactNormal) * duration;

        // If we've got a closing velocity due to acceleration build-up,
        // remove it from the new separating velocity
        if (accCausedSepVelocity < 0) {
            newSepVelocity += restitution * accCausedSepVelocity;

            // Make sure we haven't removed more than was
            // there to remove.
            if (newSepVelocity < 0)
                newSepVelocity = 0;
        }

        double deltaVelocity = newSepVelocity - separatingVelocity;

        // We apply the change in velocity to each object in proportion to
        // their inverse mass (i.e. those with lower inverse mass [higher
        // actual mass] get less change in velocity)..
        double totalInverseMass = particle[0].getInverseMass();
        if (particle[1] != null) {
            totalInverseMass += particle[1].getInverseMass();
        }

        // If all particles have infinite mass, then impulses have no effect
        if (totalInverseMass <= 0) {
            return;
        }

        // Calculate the impulse to apply
        double impulse = deltaVelocity / totalInverseMass;

        // Find the amount of impulse per unit of inverse mass
        Vector3 impulsePerIMass = contactNormal.multi(impulse);

        // Apply impulses: they are applied in the direction of the contact,
        // and are proportional to the inverse mass.
        particle[0].getVelocity().addUpdate(impulsePerIMass.multi(particle[0].getInverseMass()));
        if (particle[1] != null) {
            // Particle 1 goes in the opposite direction
            particle[1].getVelocity().addUpdate(impulsePerIMass.multi(-particle[1].getInverseMass()));
        }
    }

    //设置两个粒子碰撞后的位置
    private void resolveInterpenetration(double duration) {
        // If we don't have any penetration, skip this step.
        if (penetration <= 0) {
            return;
        }

        // The movement of each object is based on their inverse mass, so
        // total that.
        double totalInverseMass = particle[0].getInverseMass();
        if (particle[1] != null) {
            totalInverseMass += particle[1].getInverseMass();
        }

        // If all particles have infinite mass, then we do nothing
        if (totalInverseMass <= 0) {
            return;
        }

        // Find the amount of penetration resolution per unit of inverse mass
        Vector3 movePerIMass = contactNormal.multi(penetration / totalInverseMass);

        // Calculate the the movement amounts
        particleMovement[0] = movePerIMass.multi(particle[0].getInverseMass());
        if (particle[1] != null) {
            particleMovement[1] = movePerIMass.multi(-particle[1].getInverseMass());
        } else {
            particleMovement[1].clear();
        }

        // Apply the penetration resolution
        particle[0].getPosition().addUpdate(particleMovement[0]);
        if (particle[1] != null) {
            particle[1].getPosition().addUpdate(particleMovement[1]);
        }
    }
}
